package org.mbds.android.p2p.services;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import org.json.JSONException;
import org.mbds.android.location.GPSTools;
import org.mbds.android.p2p.listeners.P2pActionListener;
import org.mbds.android.p2p.listeners.P2pDnsResponseListener;
import org.mbds.android.p2p.listeners.P2pServiceDnsTextRecordListener;
import org.mbds.android.p2p.receivers.P2pBroadcastReceiver;
import org.mbds.android.p2p.serviceinfos.P2pServiceInfo;
import org.mbds.android.p2p.threads.ConnectToAPeer;
import org.mbds.android.p2p.threads.Getter;
import org.mbds.android.p2p.threads.Sender;
import org.mbds.android.p2p.threads.SocketConnexion;
import org.mbds.android.sismapp.SismappMessage;
import org.mbds.android.sismapp.SismappUser;
import org.mbds.android.sismapp.activity.LoginActivity;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener;
import android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
import android.view.View;
import android.widget.Toast;

/**
 * 
 * @author doliere
 * This service is responsible for handling P2P operations
 * and writing in a ContentProvider that will deliever informations
 * on P2P informations to activities that need them.
 */

public class P2pService extends Service{
	
	//Wifi Direct
	static WifiP2pManager mP2pWifiManager; // Get an instance of the P2P wifi manager.
	static Channel mP2pWifiChannel; //Got after initializing an instance of Wifi manager.
	static P2pBroadcastReceiver mP2pBroadcastReceiver;
	static IntentFilter mIntentFilter; // Register the service for listening to intents.
	static boolean mP2pWifiState;
	
	public static final int TYPE_SEND_MESSAGE = 10;
	public static final int TYPE_RECEIVED_MESSAGE = 20;
	public static final int TYPE_NEW_CLIENT = 30;
	public static final int TYPE_CLIENT_REGISTERED = 40;
	public static final int TYPE_MESSAGE_SENT = 50;
	
	//Inter Process Communication between service and activities.
	Messenger mMessengerSismappMessage;
	Messenger mMessengerThisService;
	
	//For handling threads in the service.
	private Looper mServiceLooper;
	private ServiceHandler mServiceHandler;
	
	//Service
	private static final String PORT = "PORT";
	private static final String USER = "USER";
	private static final String SERVICE_TYPE = "_presence._tcp.";
	private static final String INSTANCE 	= "SISMAPP";
	
	//Communications.
	ServerSocket mServerSocket;	
	ArrayList<Socket> mClientConnectes;
	Hashtable<String, WifiP2pDevice> mSismappDevices;
	ArrayList<WifiP2pInfo> mClientInfos;
	//Service discovery informations
	private static P2pActionListener mAddLocalServiceActionListener;
	
	private ArrayList<WifiP2pDevice> mSismappServiceDevices = null;
	
	private static P2pDnsResponseListener mDnsResponseListener;
	private static P2pServiceDnsTextRecordListener mDnsTextRecordListener;
	private ArrayList<WifiP2pDevice> mSismappDnsDevices = null;
	
	//Service request listener
	private static P2pActionListener mServiceRequestActionListener;
	private WifiP2pDnsSdServiceRequest mP2pServiceRequest;
	
	private static P2pActionListener mServiceDiscoveringActionListener;
	
	DnsSdTxtRecordListener txtListener;
	DnsSdServiceResponseListener servListener;
	WifiP2pDnsSdServiceInfo serviceInfo;
	Map<String, String> record;
	
	P2pServiceInfo mServiceInfo;
	
	
	private  class ServiceHandler extends Handler {
		
		public ServiceHandler(Looper looper) {
	          super(looper);
	      }
	      @Override
	      public void handleMessage(Message msg) {
	    	  switch(msg.what){
	    	  case P2pService.TYPE_NEW_CLIENT :
	    		  mMessengerSismappMessage = msg.replyTo;
	    		  notification();
	    		  break;
	    	  case P2pService.TYPE_SEND_MESSAGE :
	    		  //sendMessagesToPeers((String)msg.obj);
	    		  String chaine = "DYFI";
	    		  Message mess = Message.obtain(null, P2pService.TYPE_CLIENT_REGISTERED);
	    		  mess.obj = chaine;
	    		  try {
					mMessengerSismappMessage.send(mess);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	    		  break;
	    	  default :
	    		  initializeP2pWifiService();
	    		 /*registerSismappService(); // Register service.
		          registerDnsTextAndResponseListeners();
		          startServiceRequestAndDiscovery();*/
	    		 //initializeLocalService();
	    		  mServiceInfo = new P2pServiceInfo(P2pService.this, mP2pWifiManager, mP2pWifiChannel, mServerSocket);
	    		  mServiceInfo.initServiceInfo();
	    		  break;
	    	  }
	      }
	}
	
	
	
	@Override
	public void onCreate(){
		super.onCreate();
		HandlerThread thread = new HandlerThread("SISMAPP SERVICE PROCESS", Process.THREAD_PRIORITY_BACKGROUND);
	    thread.start();
	    
	    mServiceLooper = thread.getLooper();
	    mServiceHandler = new ServiceHandler(mServiceLooper);
	    mMessengerThisService = new Messenger(mServiceHandler);
	    mClientConnectes = new ArrayList<Socket>();
	    mClientInfos = new ArrayList<WifiP2pInfo>();
	    mSismappDevices = new Hashtable<String, WifiP2pDevice>();
	}
	@Override
	public int onStartCommand(Intent intent, int arg1, int startId){
		
		Toast.makeText(getApplicationContext(), "Service started", Toast.LENGTH_SHORT).show();
		
		Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
		return START_STICKY;
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		Toast.makeText(getApplicationContext(), "An Activity bound to this ", Toast.LENGTH_LONG).show();
		return mMessengerThisService.getBinder();
	}
	
	@SuppressLint("NewApi")
	@Override
	public void onDestroy(){
		super.onDestroy();
		unregisterReceiver(mP2pBroadcastReceiver);
		if(mP2pWifiState){
			unRegisterAllListeners();
			mP2pWifiManager.removeGroup(mP2pWifiChannel, new WifiP2pManager.ActionListener() {
				
				@Override
				public void onSuccess() {
					// TODO Auto-generated method stub
					
				}
				
				@Override
				public void onFailure(int reason) {
					// TODO Auto-generated method stub
					
				}
			});
		}
	}
	
	
	public void unRegisterAllListeners(){
	mP2pWifiManager.clearLocalServices(mP2pWifiChannel, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onFailure(int reason) {
				// TODO Auto-generated method stub
				
			}
		});
		mP2pWifiManager.clearServiceRequests(mP2pWifiChannel, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onFailure(int reason) {
				// TODO Auto-generated method stub
				
			}
		});
		Toast.makeText(getApplicationContext(), "Service destroyed", Toast.LENGTH_SHORT).show();
		mP2pWifiManager.removeLocalService(mP2pWifiChannel, mServiceInfo.getServiceInfo(), new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onFailure(int reason) {
				
			}
		});
	}
	
	
	public void registerListeners(){
		if(mP2pWifiManager == null){
			initializeP2pWifiService();
		}
		if(mServiceInfo == null){
			mServiceInfo = new P2pServiceInfo(this, mP2pWifiManager, mP2pWifiChannel, mServerSocket);
		}
		
		mServiceInfo.initServiceInfo();
	}
	
	/**
	 * @author doliere
	 * This section of the service is about setting up the P2p wifi services : a manager and a channel.
	 * And registring for peers discovery, getting peers list, establishing connections and exchanging datas...
	 */
	
	@SuppressLint("NewApi")
	public void initializeP2pWifiService(){
		mP2pWifiManager = (WifiP2pManager)getSystemService(WIFI_P2P_SERVICE);
		mP2pWifiChannel = mP2pWifiManager.initialize(getApplicationContext(), getMainLooper(), null);
		
		mIntentFilter = new IntentFilter(); 
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
		mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
		
		mP2pBroadcastReceiver = new P2pBroadcastReceiver(mP2pWifiManager, mP2pWifiChannel, this);
		registerReceiver(mP2pBroadcastReceiver, mIntentFilter);
		try {
			mServerSocket = new ServerSocket(8888);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void initializeLocalService(){
		 Map record = new HashMap();
	        record.put(PORT, String.valueOf(getServerPort()));
	        record.put(USER, LoginActivity.firstName + " " + LoginActivity.lastName);

	        // Service information.  Pass it an instance name, service type
	        // _protocol._transportlayer , and the map containing
	        // information other devices will want once they connect to this one.
	        WifiP2pDnsSdServiceInfo serviceInfo =
	                WifiP2pDnsSdServiceInfo.newInstance(INSTANCE, SERVICE_TYPE, record);
        //Add local service.
        mP2pWifiManager.addLocalService(mP2pWifiChannel, serviceInfo, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				Toast.makeText(getApplicationContext(), "Local Service added", Toast.LENGTH_SHORT).show();
			}
			
			@Override
			public void onFailure(int reason) {
				Toast.makeText(getApplicationContext(), "Local Service adding failed", Toast.LENGTH_SHORT).show();
			}
		});
        
	    txtListener = new DnsSdTxtRecordListener() {

			@Override
			public void onDnsSdTxtRecordAvailable(String arg0,
					Map<String, String> arg1, WifiP2pDevice device) {
                Toast.makeText(getApplicationContext(), "DNS TXT" + device.deviceName, Toast.LENGTH_SHORT).show();
				
			}
	    	
	    };

	    
	    servListener = new DnsSdServiceResponseListener() {
	        @Override
	        public void onDnsSdServiceAvailable(String instanceName, String registrationType,
	                WifiP2pDevice resourceType) {
                Toast.makeText(getApplicationContext(), "DNS SERVICE", Toast.LENGTH_SHORT).show();
	        }
	    };
	    
	    //Set dns text and dns response listener.
	    mP2pWifiManager.setDnsSdResponseListeners(mP2pWifiChannel, servListener, txtListener);
	    
	    //Adding service request
	    WifiP2pDnsSdServiceRequest serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
	    mP2pWifiManager.addServiceRequest(mP2pWifiChannel,
                serviceRequest,
                new WifiP2pManager.ActionListener() {
                    @Override
                    public void onSuccess() {
    	                Toast.makeText(getApplicationContext(), "ADD REQUEST SUCCESSFUL", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onFailure(int code) {
    	                Toast.makeText(getApplicationContext(), "ADD REQUEST FAILED", Toast.LENGTH_SHORT).show();
                    }
          	});
	    
	    
	    //Start service discovery.
	    mP2pWifiManager.discoverServices(mP2pWifiChannel, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				Toast.makeText(getApplicationContext(), "Local Service discovery succeeded", Toast.LENGTH_SHORT).show();
			}
			
			@Override
			public void onFailure(int reason) {
				Toast.makeText(getApplicationContext(), "Local Service discovery failed", Toast.LENGTH_SHORT).show();
			}
		});
	}
	
	
	/**
	 * This section is about registring the application for SISMAPP service discovery.
	 * So that peers can be filtered given that they provide or not a SISMAPP service.s
	 */
	
	@SuppressLint("NewApi")
	public void registerSismappService(){
		mAddLocalServiceActionListener = new P2pActionListener("ADD_LOCAL_SERVICE");
		 Map record = new HashMap();
	        record.put(PORT, String.valueOf(getServerPort()));
	        record.put(USER, LoginActivity.firstName + " " + LoginActivity.lastName);
	        WifiP2pDnsSdServiceInfo serviceInfo =
	                WifiP2pDnsSdServiceInfo.newInstance("", "_presence._tcp", record);
		mP2pWifiManager.addLocalService(mP2pWifiChannel, serviceInfo, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				Toast.makeText(getApplicationContext(), "Local Service added", Toast.LENGTH_SHORT).show();
			}
			
			@Override
			public void onFailure(int reason) {
				Toast.makeText(getApplicationContext(), "Local Service adding failed", Toast.LENGTH_SHORT).show();
			}
		});
	}
	
	@SuppressLint("NewApi")
	public void registerDnsTextAndResponseListeners(){
		mDnsTextRecordListener = new P2pServiceDnsTextRecordListener(this);
		mDnsResponseListener = new P2pDnsResponseListener(this);
		mP2pWifiManager.setDnsSdResponseListeners(mP2pWifiChannel, new WifiP2pManager.DnsSdServiceResponseListener() {
			
			@Override
			public void onDnsSdServiceAvailable(String instanceName,
					String registrationType, WifiP2pDevice srcDevice) {
				Toast.makeText(getApplicationContext(), "Local DNS Available", Toast.LENGTH_SHORT).show();
			}
		}, new WifiP2pManager.DnsSdTxtRecordListener() {
			
			@Override
			public void onDnsSdTxtRecordAvailable(String arg0,
					Map<String, String> arg1, WifiP2pDevice arg2) {
				Toast.makeText(getApplicationContext(), "Local Service Text Record available", Toast.LENGTH_SHORT).show();
			}
		});
	}
	
	@SuppressLint("NewApi")
	public void startServiceRequestAndDiscovery(){
		mServiceRequestActionListener = new P2pActionListener("SERVICE REQUEST");
		mServiceDiscoveringActionListener = new P2pActionListener("DISCOVER SERVICE");
		
		//mP2pServiceRequest = WifiP2pDnsSdServiceRequest.newInstance(P2pServiceInfo.INSTANCE_NAME, P2pServiceInfo.mProtocolType);
		
		mP2pWifiManager.addServiceRequest(mP2pWifiChannel, mP2pServiceRequest, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				Toast.makeText(getApplicationContext(), "Local Service request added", Toast.LENGTH_SHORT).show();
			}
			
			@Override
			public void onFailure(int reason) {
				Toast.makeText(getApplicationContext(), "Local Service request failed", Toast.LENGTH_SHORT).show();
			}
		});
		
		mP2pWifiManager.discoverServices(mP2pWifiChannel, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				Toast.makeText(getApplicationContext(), "Local Service discovery succeeded", Toast.LENGTH_SHORT).show();
			}
			
			@Override
			public void onFailure(int reason) {
				Toast.makeText(getApplicationContext(), "Local Service discovery failed", Toast.LENGTH_SHORT).show();
			}
		});
	}
	

	public void setP2pWifiState(boolean state){
		mP2pWifiState = state;
		if(mP2pWifiState){
			registerListeners();
		}else{
			unRegisterAllListeners();
		}
	}
	
	public void setLocalServiceRegistrationFailed(boolean state){
		if(state){
			unRegisterAllListeners();
			registerListeners();
		}
	}
	
	public void addRequestFailed(){
		mServiceInfo.initializeRequest();
	}
	
	public void addServiceFailed(){
		mServiceInfo.initialize();
	}
	
	public void startDiscoveryFailed(){
		mServiceInfo.initializeDiscovery();
	}
	
	
	//This method is called when the a new sismapp service device is discovered. 
	public void setSismappDevices(ArrayList<WifiP2pDevice> liste){
		if(mSismappServiceDevices != null && mSismappServiceDevices.size() > 0){
			mSismappServiceDevices.clear();
		}
		mSismappServiceDevices = liste;
	}
	
	
	
	//This method is called to associate a device MAC @ to the user that holds that device.
	public void addWifiP2pInfo(WifiP2pInfo info){
		mClientInfos.add(info);
		Toast.makeText(getApplicationContext(), "Connexion Availabe", Toast.LENGTH_SHORT).show();
		new SocketConnexion(P2pService.this, mServerSocket).execute(info);
	}
	
	public void openASocket(){
		if(mClientInfos.size() >0){
			WifiP2pInfo info = mClientInfos.remove(0);
			//Open a socket connexion.
			new SocketConnexion(P2pService.this, mServerSocket).execute(info);
		}
	}
	
	public void addSocket(Socket client){
		mClientConnectes.add(client);
		Toast.makeText(getApplicationContext(), "Socket added", Toast.LENGTH_SHORT).show();
	}
	
	
	public void addDnsDevice(WifiP2pDevice device){
		final WifiP2pDevice dev = device;
		if(!mSismappDevices.containsKey(device.deviceAddress)){
			mSismappDevices.put(device.deviceAddress, device);
			
			Toast.makeText(getApplicationContext(), device.deviceAddress + " Added" , Toast.LENGTH_SHORT).show();
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					new ConnectToAPeer(mP2pWifiManager, mP2pWifiChannel, P2pService.this).execute(dev);
				}
			}).start();
		}
	}
	
	public void startConnexionToAPeer(WifiP2pDevice device){
		WifiP2pConfig config = new WifiP2pConfig();
		config.deviceAddress = device.deviceAddress;
		config.groupOwnerIntent = 15;
		mP2pWifiManager.connect(mP2pWifiChannel, config, new WifiP2pManager.ActionListener() {
			
			@Override
			public void onSuccess() {
				connexionEstablished(true);
			}
			
			@Override
			public void onFailure(int reason) {
				connexionEstablished(false);
			}
		});
	}
	
	
	public void connexionEstablished(boolean bool){
		if(bool){
			Toast.makeText(getApplicationContext(), "Connexion established", Toast.LENGTH_SHORT).show();
		}else{
			Toast.makeText(getApplicationContext(), "Connexion failed...", Toast.LENGTH_SHORT).show();
		}
	}
	public void setDnsDevices(ArrayList<WifiP2pDevice> mDnsDevices){
		
		if(mSismappDnsDevices != null && mSismappDnsDevices.size() >0){
			mSismappDnsDevices.clear();
		}
		
		mSismappDnsDevices = mDnsDevices;
	}
	
	public void setServerSocket(ServerSocket server){
		mServerSocket = server;
	}
	
	public ServerSocket getServerSocket(){
		return mServerSocket;
	}
	
	public int getServerPort(){
		return mServerSocket == null ? -1 : mServerSocket.getLocalPort();
	}
	
	//Try to connect to all devices.
	public void sendSismappMessages(){
		WifiP2pConfig config = new WifiP2pConfig();
		WifiP2pDevice dev;
		for(int i=0; i<mSismappDnsDevices.size(); i++){
			dev = mSismappDnsDevices.get(i);
			config.deviceAddress = dev.deviceAddress;
			config.groupOwnerIntent = 15;
			mP2pWifiManager.connect(mP2pWifiChannel, config, new WifiP2pManager.ActionListener() {
				
				@Override
				public void onSuccess() {
					Toast.makeText(getApplicationContext(), "CONNEXION ESTABLISHED", Toast.LENGTH_SHORT).show();
				}
				
				@Override
				public void onFailure(int reason) {
					
				}
			});
		}
	}
	
	public void sendMessagesToPeers(String message){
		for(Socket client : mClientConnectes){
			new Sender(this, client).execute(message);
			new Getter(this).execute(client);
		}
	}
	
	
	//This method is called in a sender thread when a message is being sent.
	public void sismappMessageSent(boolean bool){
		Message msg = Message.obtain(null, P2pService.TYPE_MESSAGE_SENT);
		msg.obj = true;
		try {
			mMessengerSismappMessage.send(msg);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//This method is called by a getter thread to send a retrieved message back to the SismappMessage.
	public void returnMessage(String message){
		Message msg  = Message.obtain(null, P2pService.TYPE_RECEIVED_MESSAGE);
		msg.obj = message;
		try {
			mMessengerSismappMessage.send(msg);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void notification(){
		Message msg = Message.obtain(null, P2pService.TYPE_RECEIVED_MESSAGE);
		msg.obj = "HÉ VOUS QUOI QUI DIT MOI ALORS ILS, DORMEZ";
		try {
			mMessengerSismappMessage.send(msg);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public String sendMessageBack(){
		SismappMessage msg = null;
		try {
			 msg = new SismappMessage(
					new SismappUser(mServerSocket.getInetAddress().getHostAddress(), LoginActivity.lastName, LoginActivity.firstName, GPSTools.getLastKnownPosition()), 
					SismappMessage.MESSAGE_SEISMIC_ALERT_CONFIRMED);
			String mess = msg.getFormattedMessage().toString();
			return mess;
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return "";
		}
	}
	
	public ArrayList<Socket> getSockets(){
		return mClientConnectes;
	}
	
}
